﻿using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;

namespace CrmLinkBuddyWpf
{
    /// <summary>
    /// Provides CRM entity ranking to help increase CRM GUID match performance.
    /// </summary>
    internal class CrmEntityRankManager
    {
        private string _crmOrgUniqueName;
        private string _entityRankFilePath;
        private DataSet _entityRankDataSet;
        private CrmMetadataRetriever _crmMetadataRetriever;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="crmOrgUniqueName">The unique name for a CRM organization.</param>
        /// <param name="crmMetadataRetriever">An initialized instance of type CrmMetadataRetriever.</param>
        public CrmEntityRankManager(string crmOrgUniqueName, CrmMetadataRetriever crmMetadataRetriever)
        {
            _crmOrgUniqueName = crmOrgUniqueName;
            _crmMetadataRetriever = crmMetadataRetriever;
            _entityRankFilePath = DeriveEntityRankFilePath();
        }

        #region Public methods

        /// <summary>
        /// Returns a list of entity names based on previous ranking details. The entities with the most matches are near the top.
        /// </summary>
        public List<string> GetEntityNamesByMostOftenFound()
        {
            List<string> entityNames = new List<string>();

            if (!File.Exists(_entityRankFilePath))
            {
                InitializeEntityRankFile();
            }
            else
            {
                LoadEntityRanksFromFile();
            }

            DataView entityRanksDV = GetEntityRanksSorted();

            foreach (DataRowView drv in entityRanksDV)
            {
                entityNames.Add(drv["EntityLogicalName"].ToString());
            }

            List<string> entityNamesNotAlreadyListed = GetEntityNamesNotInList(entityNames);
            entityNames.AddRange(entityNamesNotAlreadyListed);

            return entityNames;
        }

        /// <summary>
        /// Increment the "FindCount" value for an entity.
        /// </summary>
        /// <param name="entityLogicalName">The logical name for the entity to increase.</param>
        public void IncreaseEntityRank(string entityLogicalName)
        {
            AddEntityToEntityRankData(entityLogicalName);
            var dataTable = _entityRankDataSet.Tables[0];
            string searchExpression = string.Format("EntityLogicalName = '{0}'", entityLogicalName);
            DataRow dr = dataTable.Select(searchExpression)[0];
            dr["FindCount"] = Convert.ToInt32(dr["FindCount"]) + 1;
        }

        /// <summary>
        /// Stores the entity ranking data to disk as an XML file (serialized from a DataSet).
        /// </summary>
        public void WriteEntityRankToFile()
        {
            if (File.Exists(_entityRankFilePath))
            {
                File.Delete(_entityRankFilePath);
            }

            _entityRankDataSet.WriteXml(_entityRankFilePath);
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Returns the full path of this application's executable file.
        /// </summary>
        private string GetExecutingAssemblyDirectory()
        {
            string codeBase = Assembly.GetExecutingAssembly().CodeBase;
            UriBuilder uri = new UriBuilder(codeBase);
            string path = Uri.UnescapeDataString(uri.Path);
            return Path.GetDirectoryName(path);
        }

        /// <summary>
        /// Derives a path and file name for the XML file that contains entity ranking details for a CRM organization.
        /// </summary>
        private string DeriveEntityRankFilePath()
        {
            string path = GetExecutingAssemblyDirectory();
            if (!path.EndsWith("\\"))
            {
                path += "\\";
            }
            return path + _crmOrgUniqueName + ".xml";
        }

        /// <summary>
        /// Establish the entity ranking data set using common and custom entity names.
        /// </summary>
        private void InitializeEntityRankFile()
        {
            CreateEntityRankDataSet();
            AddEntityToEntityRankData("account");
            AddEntityToEntityRankData("contact");
            AddEntityToEntityRankData("lead");
            AddEntityToEntityRankData("opportunity");
            List<string> customEntityNameList = _crmMetadataRetriever.GetCustomEntityNames();
            foreach (string entityLogicalName in customEntityNameList)
            {
                AddEntityToEntityRankData(entityLogicalName);
            }
        }

        /// <summary>
        /// Adds an entity name to the collection of ranked entities.
        /// </summary>
        private void AddEntityToEntityRankData(string entityLogicalName)
        {
            var dataTable = _entityRankDataSet.Tables[0];
            string searchExpression = string.Format("EntityLogicalName = '{0}'", entityLogicalName);
            if (dataTable.Select(searchExpression).Count() == 0)
            {
                dataTable.Rows.Add(new Object[] { entityLogicalName, 0 });
            }
        }

        /// <summary>
        /// Creates a DataSet that stores entity ranking details.
        /// </summary>
        private void CreateEntityRankDataSet()
        {
            _entityRankDataSet = new DataSet("Entities");
            DataTable dt = new DataTable("Entity");
            dt.Columns.Add(new DataColumn("EntityLogicalName", typeof(string)));
            dt.Columns.Add(new DataColumn("FindCount", typeof(int)));
            _entityRankDataSet.Tables.Add(dt);
        }

        /// <summary>
        /// Loads entity ranking data from an XML file.
        /// </summary>
        private void LoadEntityRanksFromFile()
        {
            _entityRankDataSet = new DataSet();
            _entityRankDataSet.ReadXml(_entityRankFilePath, XmlReadMode.InferSchema);
        }

        /// <summary>
        /// Returns a DataView representation of the entity ranking data, with the most-found entities at the top.
        /// </summary>
        private DataView GetEntityRanksSorted()
        {
            DataView dv = new DataView(_entityRankDataSet.Tables[0]);
            dv.Sort = "FindCount DESC";
            return dv;
        }

        /// <summary>
        /// Returns a list of entity names that are queryable and are not already in the provided list of entity names.
        /// </summary>
        /// <param name="entityNamesToExclude">List of entity names to exclude from the returning list.</param>
        private List<string> GetEntityNamesNotInList(List<string> entityNamesToExclude)
        {
            var allEntityNames = _crmMetadataRetriever.GetQueryableEntityLogicalNames();
            var remainingEntityNames = new List<string>();
            var entitiesToSkip = EntitiesToExcludeFromQueries();

            foreach (string entityLogicalName in allEntityNames)
            { 
                if (!entityNamesToExclude.Contains(entityLogicalName) && !entitiesToSkip.Contains(entityLogicalName))
                {
                    remainingEntityNames.Add(entityLogicalName);
                }
            }
            return remainingEntityNames;
        }

        /// <summary>
        /// Define the list of entities that this application cannot query or cannot display in a form.
        /// </summary>
        private List<string> EntitiesToExcludeFromQueries()
        {
            List<string> list = new List<string>();
            list.Add("calendarrule");
            list.Add("customeropportunityrole");
            list.Add("customerrelationship");
            list.Add("integrationstatus");
            list.Add("pluginassembly");
            list.Add("plugintype");
            list.Add("plugintypestatistic");
            list.Add("postregarding");
            list.Add("postrole");
            list.Add("principalattributeaccessmap");
            list.Add("principalentitymap");
            list.Add("report");
            list.Add("sdkmessage");
            list.Add("sdkmessagefilter");
            list.Add("sdkmessageprocessingstep");
            list.Add("sdkmessageprocessingstepimage");
            list.Add("sdkmessageprocessingstepsecureconfig");
            list.Add("serviceendpoint");
            list.Add("sharepointdocument");
            list.Add("subscription");
            list.Add("subscriptionclients");
            list.Add("subscriptionmanuallytrackedobject");
            list.Add("tracelog");
            list.Add("workflowwaitsubscription");
            return list;
        }
        
        #endregion
    }
}
